package com.studerb.tasks.headers;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Calendar;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Future;

import org.apache.commons.io.IOUtils;
import org.apache.commons.net.io.DotTerminatedMessageReader;
import org.apache.commons.net.nntp.NNTPReply;
import org.springframework.beans.factory.annotation.Autowired;

import com.studerb.model.NewsGroup;
import com.studerb.service.interfaces.IHeaderService;
import com.studerb.service.interfaces.INewsGroupService;
import com.studerb.service.interfaces.ISettingsService;
import com.studerb.service.interfaces.ITaskService;
import com.studerb.tasks.AbstractTask;
import com.studerb.util.Constants;

public class XoverNewCommandTask extends AbstractTask<Void, Void> {
    @Autowired private ISettingsService settingsService;
    @Autowired private IHeaderService headerService;
    @Autowired private INewsGroupService newsGroupService;
    @Autowired private ITaskService taskService;
    private NewsGroup newsGroup;
    private int XOVER_BATCH_SIZE;
    private BlockingQueue<String> queue;
    private Future consumer;
    private int current;
    private int first;
    private int last;
    private int totalDownloaded = 0;

    public void setNewsGroup(NewsGroup newsGroup) {
        this.newsGroup = newsGroup;
    }

    @Override public void init() {
        XOVER_BATCH_SIZE = Integer.parseInt(settingsService.getSetting("tasks.xoverAllCommandTask.batchSize"));
        queue = new ArrayBlockingQueue<String>(XOVER_BATCH_SIZE);
        title = "Downloading All Headers";
        determinable = true;
        connectionNeeded = true;
    }

    @Override protected Void doInBackground() throws Exception {
        logger.debug("Starting do In background");
        doGroupCommand();
        first = groupCommandReply.getFirstPost();
        last = groupCommandReply.getLastPost();
        int newFirst = getFirstArticle(newsGroup, first);
        if(newFirst > last){
            setProgress(100);
            return null;
        }
        else{
            first = newFirst;
        }

        setTitle("Downloading " + (last - first) + " New Headers: " + newsGroup.getGroupName());
        logger.debug("First: " + first + "  / Last: " + last);
        current = first;
        int start;
        int end;
        logger.debug("Starting xover consumer");
        try{
            consumer = taskService.submit(headerService.getXoverCommandProcessor(queue, newsGroup));
            while(current <= last && !isCancelled() && !consumer.isDone()){
                start = current;
                end = current + XOVER_BATCH_SIZE > last ? last : current + XOVER_BATCH_SIZE;
                downloadBatch(start, end);
                current = end + 1;
                logger.debug("Current: " + current);
            }
            setMessage("Finished Downloading - Waiting on Database");
        }
        finally{
            queue.put(Constants.END_OF_QUEUE);
        }
        consumer.get();
        return null;
    }

    private void downloadBatch(int start, int end) throws Exception {
        logger.debug(String.format("Downloading Headers %d - %d", start, end));
        setMessage(String.format("Downloading Headers %d - %d", start, end));
        BufferedReader reader = new BufferedReader(new DotTerminatedMessageReader(nntpConnection.getReader()));
        String tempLine;
        int loopCount = 0;
        try{
            String range = start + "-" + end;
            int reply = nntpConnection.xover(range);
            if(reply != NNTPReply.XOVER_INFORMATION_FOLLOWS){
                throw new IOException("Bad XOVER reply: " + nntpConnection.getReplyString());
            }
            while( !isCancelled() && !consumer.isDone()){
                tempLine = reader.readLine();
                if(tempLine == null){
                    break;
                }
                else{
                    queue.put(tempLine);
                    loopCount++;
                    totalDownloaded++;
                    checkProgress();
                }
            }
            logger.debug("Downloaded: " + loopCount + " headers");
            newsGroup.setLastUpdatedHeaders(Calendar.getInstance());
            newsGroupService.updateNewsGroup(newsGroup);
        }
        finally{
            logger.info(String.format("Downloaded %d header lines.", loopCount));
            IOUtils.closeQuietly(reader);
        }
    }

    private void checkProgress() {
        setProgress(totalDownloaded + first, first, last + 1);
    }

    private void doGroupCommand() throws IOException {
        setMessage("Sending Group Command");
        if(newsGroup == null){
            throw new IllegalStateException("Must set newsgroup before executing");
        }
        groupCommandReply = nntpConnection.groupCommand(newsGroup.getGroupName());
        logger.debug("Group command reply: " + nntpConnection.getReplyString());
    }

    /*
     * class Consumer implements Callable {
     * @Override public Object call() throws Exception { logger.debug("Consumer starting"); List<String> badHeaders =
     * new ArrayList<String>(); List<Header> tempHeaders = new ArrayList<Header>(); int totalLines = 0; int goodLines =
     * 0; String nextLine = null; Long lastArticleNumber = new Long( -1); try{ do{ nextLine = queue.take();
     * totalLines++; Header tempHeader = Header.createHeader(nextLine, newsGroup); if(tempHeader != null){ goodLines++;
     * tempHeaders.add(tempHeader); } else if( !nextLine.equals(Constants.END_OF_QUEUE)){ badHeaders.add(nextLine); }
     * if(tempHeader.getArticleNumber() <= lastArticleNumber){ throw new
     * RuntimeException("Error: last downloaded article number greater than previous..."); } if(
     * !tempHeaders.isEmpty()){ if(tempHeaders.size() % DB_BATCH_SIZE == 0 || nextLine.equals(Constants.END_OF_QUEUE)){
     * headerService.saveHeaders(tempHeaders, newsGroup); logger.debug("saved/updated " + tempHeaders.size() +
     * " headers"); tempHeaders.clear(); } } } while( !nextLine.equals(Constants.END_OF_QUEUE)); }
     * catch(InterruptedException interrupted){ logger.debug("Consumer interrupted - returning"); return null; }
     * finally{ queue.clear(); } logger.debug("***Bad Headers: "); for(String badHeader : badHeaders){
     * logger.warn("\t\t" + badHeader); } logger.info(String.format("XOverConsumer: processed: %d / %d for group: %s",
     * goodLines, totalLines, newsGroup.getGroupName())); return null; } }
     */

    /**
     * Returns the maximum article number to download for this group. If the header service returns a null value from
     * {code getMaxArticle(newsGroup)} then we just get the group command provided first article;
     * @param newsGroup
     * @param last
     * @return first articleNumber of header needed to download
     */
    private int getFirstArticle(NewsGroup newsGroup, int previousFirst) {
        Long maxTemp = newsGroupService.getMaxArticleNumber(newsGroup);
        if(maxTemp == null){
            logger.debug("First Article is group provided first: " + previousFirst);
            return previousFirst;
        }
        else{
            // add 1 to max to go to first article to be downloaded
            int max = maxTemp.intValue() + 1;
            logger.debug("First article changed to: " + max + " from " + previousFirst);
            return max;
        }
    }
}
